package org.infodavid.common.test.model;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;

import org.infodavid.common.reflect.FieldUtil;
import org.infodavid.common.reflect.MethodUtil;

/**
 * The Class GettersSettersAssertions.
 */
public final class GettersSettersAssertions {

	/**
	 * The Constructor.
	 */
	private GettersSettersAssertions() {
		super();
	}

	/**
	 * Check if all the fields are set and have no null value. <br>
	 * To work, the fields, getters and setters must have the same name, for example: fieldName must
	 * have one getter getFieldName and one setter setFieldName.
	 * @param obj the object to use
	 * @param modifiers the modifiers
	 * @return true, if successful
	 * @throws IllegalAccessException the illegal access exception
	 */
	public static boolean areFieldsSet(final Object obj, final int... modifiers)
	    throws IllegalAccessException {
		final Iterator<Field> fields =
		    FieldUtil.getInstance().getFields(obj.getClass(), null).values().iterator();
		boolean set = true;
		Field field;

		Arrays.sort(modifiers);

		while (fields.hasNext() && set) {
			field = fields.next();

			field.setAccessible(true);

			if (Arrays.binarySearch(modifiers, field.getModifiers()) >= 0 && field.get(obj) == null) {
				set = false;
			}
		}

		return set;
	}

	/**
	 * Check if all attribute are set to null value.<br>
	 * To work, the fields, getters and setters must have the same name, for example: fieldName must
	 * have one getter getFieldName and one setter setFieldName.
	 * @param obj the object to use
	 * @param modifiers the modifiers
	 * @return true, if successful
	 * @throws IllegalAccessException the illegal access exception
	 */
	public static boolean areFieldsSetToNull(final Object obj, final int... modifiers)
	    throws IllegalAccessException {
		final Iterator<Field> fields =
		    FieldUtil.getInstance().getFields(obj.getClass(), null).values().iterator();
		boolean set = true;
		Field field;

		Arrays.sort(modifiers);

		while (fields.hasNext() && set) {
			field = fields.next();

			field.setAccessible(true);

			if (Arrays.binarySearch(modifiers, field.getModifiers()) >= 0 && field.get(obj) != null) {
				set = false;
			}
		}

		return set;
	}

	/**
	 * Check if data returned by the getter method is equal to the data set using the setter method,
	 * for all the attributes of the given object.<br>
	 * To work, the fields, getters and setters must have the same name, for example: fieldName must
	 * have one getter getFieldName and one setter setFieldName.
	 * @param obj the object to use
	 * @param modifiers the modifiers
	 * @throws Exception the exception
	 */
	public static void assertGettersSetters(final Object obj, final int... modifiers)
	    throws Exception {
		final Map<String,Method> getters = MethodUtil.getInstance().getGetters(obj.getClass());
		final Map<String,Method> setters = MethodUtil.getInstance().getSetters(obj.getClass());
		Method setter;
		Object value;
		String name;

		for (final Method getter : getters.values()) {
			name = getter.getName();

			if (name.startsWith("is")) {
				name = name.replaceFirst("is", "");
			}
			else if (name.startsWith("get")) {
				name = name.replaceFirst("get", "");
			}

			setter = setters.get(name);

			if (setter != null) {
				value = GeneratorUtil.getValue(setter.getParameterTypes()[0]);
				setter.invoke(obj, value);

				CompareAssertions.assertEquals(
				    "Data set by the setter is not the data returned by the getter", value,
				    getter.invoke(obj));
			}
		}
	}

	/**
	 * Check value.
	 * @param type the type
	 * @param obj1 the obj1
	 * @param obj2 the obj2
	 * @return true, if successful
	 */
	@SuppressWarnings("rawtypes")
	public static boolean checkValue(final Class type, final Object obj1, final Object obj2) {
		boolean valid = false;

		if (type.isPrimitive()) {
			if (type == int.class || type == long.class || type == short.class || type == float.class
			    || type == double.class) {
				valid = ((Number)obj1).doubleValue() == ((Number)obj2).doubleValue();
			}
			else if (type == boolean.class) {
				valid = ((Boolean)obj1).booleanValue() == ((Boolean)obj2).booleanValue();
			}
		}
		else {
			valid = obj1.equals(obj2);
		}

		return valid;
	}

}
